package net.orange.architecture.db;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.Iterator;

import net.orange.architecture.audit.DatabaseAuditInfo;
import net.orange.architecture.audit.IAuditInfo;
import net.orange.architecture.audit.IAuditable;
import net.orange.architecture.audit.IAuditor;
import net.orange.architecture.exception.DatabaseException;
import net.orange.architecture.model.BaseValueObject;

import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.Session;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

/**
 * The base database access object of the Hibernate modal,Include some standard
 * database operations (list/find/new/delete/update/......).
 */
public class HibernateObjectManager extends HibernateDaoSupport implements
		IAuditable, IObjectManager {

	/**
	 * The persistent object class name
	 */
	private String valueObjectClassName;

	/**
	 * The persistent object class
	 */
	private Class valueObjectClass;

	/**
	 * The sql formatter
	 */
	protected HibernateHQLFormator hqlformator;

	/**
	 * The database auditors
	 */
	protected Collection<IAuditor> auditors;

	/**
	 * Default Constructor.
	 */
	public HibernateObjectManager() {

	}

	/**
	 * The constructor with the the value object class
	 * 
	 * @param c0
	 *            The the persistent value object class.
	 */
	public HibernateObjectManager(Class c0) {
		super();
		valueObjectClass = c0;
		hqlformator = new HibernateHQLFormator(c0);
	}

	/**
	 * The constructor with the the value object class name.
	 * 
	 * @param c0
	 *            The persistent value object class name.
	 */
	public HibernateObjectManager(String s0) throws Exception {
		super();
		setValueObjectClassName(s0);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see net.orange.architecture.db.IBaseDao#getValueObjectClassName()
	 */
	public String getValueObjectClassName() {
		return valueObjectClassName;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * net.orange.architecture.db.IBaseDao#setValueObjectClassName(java.lang
	 * .String)
	 */
	public void setValueObjectClassName(String valueObjectClassName)
			throws Exception {
		this.valueObjectClassName = valueObjectClassName;
		this.valueObjectClass = Class.forName(valueObjectClassName);
		hqlformator = new HibernateHQLFormator(valueObjectClass);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see net.orange.architecture.db.IBaseDao#list(net.orange.architecture.db.
	 * Conditions)
	 */
	public Collection list(final Conditions cds) throws Exception {
		HibernateCallback callback = new HibernateCallback() {
			public Object doInHibernate(Session session)
					throws HibernateException, SQLException {
				try {
					String s = "from " + getEntityName()
							+ hqlformator.toSQL(cds);

					long consume = System.currentTimeMillis();

					Query query = session.createQuery(s);
					query.setCacheable(true);
					SetParameter(query, cds);

					notifyAuditor(s, System.currentTimeMillis() - consume);

					return query.list();
				} catch (Exception e) {
					throw new HibernateException(e);
				}
			}
		};
		return (Collection) getHibernateTemplate().execute(callback);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * net.orange.architecture.db.IBaseDao#list2(net.orange.architecture.db.
	 * ListConditions)
	 */
	public Collection list2(final ListConditions cds) throws Exception {

		HibernateCallback callback = new HibernateCallback() {
			public Object doInHibernate(Session session)
					throws HibernateException, SQLException {
				try {
					String s = "";

					if (cds == null)
						s = "from " + getEntityName();
					else
						s = "from " + getEntityName() + hqlformator.toSQL(cds) + " order by pk desc";
					// + SQLUtility.toSQL(cds.getOrders());

					long consume = System.currentTimeMillis();

					Query query = session.createQuery(s);
					query.setCacheable(true);
					SetParameter(query, cds);

					query.setMaxResults(cds.getPageSize());
					query.setFirstResult(cds.getPageSize()
							* (cds.getCurrPage() - 1));

					Collection c = query.list();
					cds.setRecordCount(getRecordsNumber(s, cds));

					notifyAuditor(s, System.currentTimeMillis() - consume);
					return c;
				} catch (Exception e) {
					throw new HibernateException(e);
				}
			}
		};

		return (Collection) getHibernateTemplate().execute(callback);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see net.orange.architecture.db.IBaseDao#list(net.orange.architecture.db.
	 * Conditions, net.orange.architecture.db.Orders)
	 */
	public Collection list(final Conditions cds, final Orders orders)
			throws Exception {
		HibernateCallback callback = new HibernateCallback() {
			public Object doInHibernate(Session session)
					throws HibernateException, SQLException {
				try {
					String s = "from " + getEntityName()
							+ hqlformator.toSQL(cds) + " "
							+ hqlformator.toSQL(orders);
					long consume = System.currentTimeMillis();

					Query query = session.createQuery(s);
					query.setCacheable(true);
					SetParameter(query, cds);

					notifyAuditor(s, System.currentTimeMillis() - consume);
					return query.list();
				} catch (Exception e) {
					throw new HibernateException(e);
				}
			}
		};
		return (Collection) getHibernateTemplate().execute(callback);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see net.orange.architecture.db.IBaseDao#list(java.lang.String)
	 */
	public Collection list(final String s) throws Exception {
		HibernateCallback callback = new HibernateCallback() {
			public Object doInHibernate(Session session)
					throws HibernateException, SQLException {
				long consume = System.currentTimeMillis();

				Query query = session.createQuery(s);
				query.setCacheable(true);

				notifyAuditor(s, System.currentTimeMillis() - consume);
				return query.list();
			}
		};
		return (Collection) getHibernateTemplate().execute(callback);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see net.orange.architecture.db.IBaseDao#find(long)
	 */
	public BaseValueObject find(long id) throws Exception {
		try {
			long consume = System.currentTimeMillis();

			BaseValueObject d0 = (BaseValueObject) getHibernateTemplate().get(
					valueObjectClass, new Long(id));

			notifyAuditor("search object: " + valueObjectClass + ":" + id,
					System.currentTimeMillis() - consume);

			return (BaseValueObject) d0;
		} catch (Exception e) {
			throw new DatabaseException(e);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see net.orange.architecture.db.IBaseDao#find(java.lang.String,
	 * java.lang.String)
	 */
	public BaseValueObject find(String field, String code) throws Exception {
		try {
			BaseValueObject bvo = null;

			String s = "from " + getEntityName() + " where "
					+ getSimpleEntityName() + "." + field + " = ?";
			long consume = System.currentTimeMillis();

			Collection c = getHibernateTemplate().find(s, code);

			if (c != null && c.size() > 0) {
				Iterator iter = c.iterator();
				if (iter != null && iter.hasNext())
					bvo = (BaseValueObject) c.iterator().next();
			}

			notifyAuditor(s, System.currentTimeMillis() - consume);

			return bvo;
		} catch (Exception e) {
			throw new DatabaseException(e);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see net.orange.architecture.db.IBaseDao#find(net.orange.architecture.db.
	 * Conditions)
	 */
	public BaseValueObject find(Conditions cds) throws Exception {
		Collection collection = list(cds);

		if (collection == null || collection.iterator() == null
				|| !collection.iterator().hasNext())
			return null;

		BaseValueObject valueObject = (BaseValueObject) collection.iterator()
				.next();
		return valueObject;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * net.orange.architecture.db.IBaseDao#create(net.orange.architecture.vo
	 * .BaseValueObject)
	 */
	public boolean create(BaseValueObject b0) throws Exception {
		try {
			long consume = System.currentTimeMillis();

			getHibernateTemplate().save(b0);
			getHibernateTemplate().flush();

			notifyAuditor("create object: " + b0, System.currentTimeMillis()
					- consume);
			return true;
		} catch (Exception e) {
			throw new DatabaseException(e);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * net.orange.architecture.db.IBaseDao#update(net.orange.architecture.vo
	 * .BaseValueObject)
	 */
	public boolean update(BaseValueObject b0) throws Exception {
		try {
			long consume = System.currentTimeMillis();

//			getHibernateTemplate().refresh(b0);
			getHibernateTemplate().merge(b0);
			// getHibernateTemplate().update(b0);
			getHibernateTemplate().flush();
			notifyAuditor("update object: " + b0, System.currentTimeMillis()
					- consume);
			return true;
		} catch (Exception e) {
			throw new DatabaseException(e);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * net.orange.architecture.db.IBaseDao#createOrUpdate(net.orange.architecture
	 * .vo.BaseValueObject)
	 */
	public boolean createOrUpdate(BaseValueObject b0) throws Exception {
		try {
			long consume = System.currentTimeMillis();

			getHibernateTemplate().merge(b0);
			getHibernateTemplate().flush();

			notifyAuditor("create or update object: " + b0,
					System.currentTimeMillis() - consume);
			return true;
		} catch (Exception e) {
			throw new DatabaseException(e);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * net.orange.architecture.db.IBaseDao#delete(net.orange.architecture.vo
	 * .BaseValueObject)
	 */
	public boolean delete(BaseValueObject b0) throws Exception {
		try {
			long consume = System.currentTimeMillis();
			// getHibernateTemplate().refresh(b0);
			getHibernateTemplate().delete(b0);
			getHibernateTemplate().flush();

			notifyAuditor("remove object: " + b0, System.currentTimeMillis()
					- consume);
			return true;
		} catch (Exception e) {
			throw new DatabaseException(e);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see net.orange.architecture.db.IBaseDao#delete(long)
	 */
	public boolean delete(long id) throws Exception {
		try {

			BaseValueObject b = find(id);

			if (b != null)
				delete(b);

			return true;
		} catch (Exception e) {
			throw new DatabaseException(e);
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * net.orange.architecture.audit.IAuditable#addAuditor(net.orange.architecture
	 * .audit.IAuditor)
	 */
	public void addAuditor(IAuditor auditor) {
		if (auditors == null)
			auditors = new ArrayList<IAuditor>();
		auditors.add(auditor);

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see net.orange.architecture.audit.IAuditable#removeAuditor(net.orange.
	 * architecture.audit.IAuditor)
	 */
	public void removeAuditor(IAuditor auditor) {
		if (auditors != null)
			auditors.remove(auditor);
	}

	/**
	 * Notify the auditor
	 * 
	 * @param sql
	 *            The database operation sql statement.
	 * @param consume
	 *            The time consume.
	 */
	protected void notifyAuditor(String sql, long consume) {
		notifyAuditor(new DatabaseAuditInfo(sql, consume));
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see net.orange.architecture.audit.IAuditable#notifyAuditor(net.orange.
	 * architecture.audit.IAuditInfo)
	 */
	public void notifyAuditor(IAuditInfo info) {
		if (auditors != null)
			for (IAuditor auditor : auditors)
				if (auditor != null)
					auditor.update(info);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see net.orange.architecture.db.IBaseDao#batchUpdate(java.lang.String)
	 */
	public int batchUpdate(String s) throws Exception {
		return getHibernateTemplate().bulkUpdate(s);
	}

	/**
	 * Set the parameters to query.
	 * 
	 * @param query
	 *            The query
	 * @param cs
	 *            The coditions collections contain the parameters.
	 */
	protected static void SetParameter(Query query, Conditions cs) {
		int index = 0;

		for (ConditionPair element : cs.toArray()) {
			boolean indexed = SetParameter(query, index, element.getCondition());

			if (indexed)
				index++;
		}
	}

	/**
	 * Set the query parameter.
	 * 
	 * @param query
	 *            The query object
	 * @param index
	 *            The parameter index in query.
	 * @param cs
	 *            The conditions
	 * @return Success or not.
	 */
	protected static boolean SetParameter(Query query, int index, Condition cs) {
		Object value = cs.getFieldValue();
		boolean indexed = true;

		if (value instanceof String
				&& cs.getOperator().getOp() == Operator.LIKE)
			query.setString(index, ((String) value) + "%");
		else if (value instanceof String
				&& cs.getOperator().getOp() == Operator.STR_IN)
			query.setString(index, "%" + ((String) value) + "%");
		else if (value instanceof String
				&& cs.getOperator().getOp() == Operator.EQUAL)
			query.setString(index, (String) value);
		else if (value instanceof Integer)
			query.setInteger(index, ((Integer) value).intValue());
		else if (value instanceof Long)
			query.setLong(index, ((Long) value).longValue());
		else if (value instanceof Float)
			query.setFloat(index, ((Float) value).floatValue());
		else if (value instanceof Double)
			query.setDouble(index, ((Double) value).doubleValue());
		else if (value instanceof Date)
			query.setDate(index, ((Date) value));
		else
			indexed = false;

		return indexed;
	}

	/**
	 * Get the record number according the sql statement.
	 * 
	 * @param s
	 *            the sql statement.
	 * @return The record number according the sql statement.
	 * @throws Exception
	 */
	public long getRecordsNumber(final String s, final ListConditions l)
			throws Exception {

		HibernateCallback callback = new HibernateCallback() {
			public Object doInHibernate(Session session)
					throws HibernateException, SQLException {
				String cs = "select count(*) " + s;

				long consume = System.currentTimeMillis();

				Query query = session.createQuery(cs);
				query.setCacheable(true);
				SetParameter(query, l);

				notifyAuditor(s, System.currentTimeMillis() - consume);

				return (Long) query.list().get(0);
			}
		};
		Long rows = (Long) getHibernateTemplate().execute(callback);
		return rows.intValue();
	}

	/**
	 * Retrieve the value object name and alias.
	 * 
	 * @return Returns the value object name and alias.
	 */
	protected String getEntityName() {
		String s0 = valueObjectClass.getName();
		String s1 = s0.substring(s0.lastIndexOf(".") + 1, s0.length());
		return s1 + " as " + s1.toLowerCase();
	}

	/**
	 * Retrieve the value object name.
	 * 
	 * @return the value object name.
	 */
	protected String getSimpleEntityName() {
		String s0 = valueObjectClass.getName();
		String s1 = s0.substring(s0.lastIndexOf(".") + 1, s0.length());
		return s1.toLowerCase();
	}

	/**
	 * @return Returns the auditors.
	 */
	public Collection<IAuditor> getAuditors() {
		return auditors;
	}

	/**
	 * @param auditors
	 *            The auditors to set.
	 */
	public void setAuditors(Collection<IAuditor> auditors) {
		this.auditors = auditors;
	}
}